#include "XMLReader.h"

static void Trim( std::string& string)
{
	if(string.length() == 0) return;
	for(unsigned int i = 0; i < string.length() && (string[i] == ' ' || string[i] == 9);)
	{
		if(string[i] == ' ' || string[i] == 9)
			string = string.substr(1);
	}
	for(unsigned int i = string.length()-1; i > 0 && (string[i] == ' ' || string[i] == 9); i--)
	{
		if(string[i] == ' ' || string[i] == 9)
			string.pop_back();
	}
}

XMLElement::XMLElement(std::string tagName)
{
	this->m_TagName = tagName;
}

XMLElement::~XMLElement()
{
	for(std::list<XMLElement*>::iterator it = m_ChildElements.begin(); it != m_ChildElements.end(); ++it)
	{
		delete (*it);
	}
}

std::string XMLElement::getTagName()
{
	return m_TagName;
}

void XMLElement::setTagName(std::string tagName) 
{
	this->m_TagName = tagName;
}

std::string XMLElement::getText() 
{
	return m_Text;
}

void XMLElement::setText(std::string text)
{
	this->m_Text = text;
}

std::string XMLElement::getAttribute(std::string name) 
{
	std::map<const HashedString, const std::string>::iterator it = m_Attributes.find(name.c_str());
	if(it != m_Attributes.end())
		return std::string(it->second);
	return std::string("");
}

void XMLElement::setAttribute(std::string name, std::string value) 
{
	m_Attributes.insert(std::pair<const HashedString, const std::string>(name.c_str(), value.c_str()));
}

void XMLElement::addChildElement(XMLElement* element) 
{
	m_ChildElements.insert(m_ChildElements.begin(), element);
}

XMLElement::List_t& XMLElement::getChildElements() 
{
	return m_ChildElements;
}

XMLReader::XMLReader(std::ifstream& stream):reader(stream) 
{
	currentElement = 0;
	cdata_start.push_back('<');
	cdata_start.push_back('!');
	cdata_start.push_back('[');
	cdata_start.push_back('C');
	cdata_start.push_back('D');
	cdata_start.push_back('A');
	cdata_start.push_back('T');
	cdata_start.push_back('A');
	cdata_start.push_back('[');
	cdata_end.push_back(']');
	cdata_end.push_back(']');
	cdata_end.push_back('>');
}

XMLElement* XMLReader::parse() 
{
	// skip xml declaration or DocTypes
	skipPrologs();

	while (true)
	{
		unsigned int index;
		std::string tagName;

		// remove the prepend or trailing white spaces
		std::string currentTag = readTag();
		Trim(currentTag);
		if (currentTag.substr(0,2).compare("</") == 0) 
		{
			// close tag
			tagName = currentTag.substr(2, currentTag.length()-3);

			// no open tag
			if (currentElement == 0)
			{
				return 0;
			}

			// close tag does not match with open tag
			if (!tagName.compare(currentElement->getTagName()) == 0)
			{
				return 0;
			}

			if (elements.empty()) 
			{
				// document processing is over
				return currentElement;
			} else 
			{
				// pop up the previous open tag
				currentElement = elements.top();
				elements.pop();
			}
		} else 
		{
			// open tag or tag with both open and close tags
			index = currentTag.find_first_of(" ");
			if (index == currentTag.npos) 
			{
				// tag with no attributes
				if (currentTag.substr( currentTag.length()-2, 2).compare("/>") == 0) 
				{
					// close tag as well
					tagName = currentTag.substr(1, currentTag.length()-2);
					currentTag = "/>";
				} else 
				{
					// open tag
					tagName = currentTag.substr(1, currentTag.length()-2);
					currentTag = ">";
				}
			} else
			{
				// tag with attributes
				tagName = currentTag.substr(1, index);
				currentTag = currentTag.substr(index+1);
			}

			// create new element
			Trim(tagName);
			XMLElement* element = new XMLElement(tagName);

			// parse the attributes
			bool isTagClosed = false;
			while (currentTag.length() > 0) 
			{
				// remove the prepend or trailing white spaces

				Trim(currentTag);

				if (currentTag.compare("/>") == 0)
				{
					// close tag
					isTagClosed = true;
					break;
				} else if (currentTag.compare(">") == 0) 
				{
					// open tag
					break;
				}

				index = currentTag.find_first_of('=');
				if (index == currentTag.npos)
				{
					return 0;
				}

				// get attribute name
				std::string attributeName = currentTag.substr(0, index);
				Trim(attributeName);
				currentTag = currentTag.substr(index+1);
				Trim(currentTag);
				// get attribute value
				std::string attributeValue;
				bool isQuoted = true;
				if (currentTag.substr(0,1).compare("\"") == 0)
				{
					index = currentTag.find_first_of('"', 1);
				} else if (currentTag.substr(0,1).compare("'") == 0) 
				{
					index = currentTag.find_first_of('\'', 1);
				} else
				{
					isQuoted = false;
					index = currentTag.find_first_of(' ');
					if (index == currentTag.npos) 
					{
						index = currentTag.find_first_of('>');
						if (index ==  currentTag.npos)
						{
							index = currentTag.find_first_of('/');
						}
					}
				}

				if (index == currentTag.npos)
				{
					return 0;
				}

				if (isQuoted) 
				{
					attributeValue = currentTag.substr(1, index-1);
				} else
				{
					attributeValue = currentTag.substr(0, index);
				}

				// add attribute to the new element
				element->setAttribute(attributeName, attributeValue);

				currentTag = currentTag.substr(index+1);
			}

			// read the text between the open and close tag
			if (!isTagClosed) 
			{
				element->setText(readText());
			}

			// add new element as a child element of
			// the current element
			if (currentElement != 0)
			{
				currentElement->addChildElement(element);
			}

			if (!isTagClosed) 
			{
				if (currentElement != 0) 
				{
					elements.push(currentElement);
				}

				currentElement = element;
			} else if (currentElement == 0)
			{
				// only has one tag in the document
				return element;
			}
		}
	}
}

char XMLReader::peek() 
{
	char chr;
	do
	{
		reader.read(&chr, 1);
	}
	while(chr == 10);
	reader.seekg(-1, std::ios_base::cur);
	return chr;
}

void XMLReader::peek(std::vector<int>& buffer) 
{
	std::streamoff place = reader.tellg();
	char temp;
	for (unsigned int i=0; i<buffer.size(); i++) 
	{
		reader.read(&temp, 1);
		if(!reader.eof())
		{
			buffer[i] = temp;
		} else
			buffer[i] = 0;
	}
	reader.clear();
	reader.seekg(place, std::ios_base::beg);
}

void XMLReader::skipWhitespace() {
	while ((peek() == ' ' || peek() == '\n') && !reader.eof())
	{
		reader.seekg(1, std::ios_base::cur);
	}
}

void XMLReader::skipProlog()
{
	// skip "<?" or "<!"
	reader.seekg(2, std::ios_base::cur);

	while (true) {
		int next = peek();

		if (next == '>') 
		{
			reader.seekg(1, std::ios_base::cur);
			break;
		} else if (next == '<')
		{
			// nesting prolog
			skipProlog();
		} else
		{
			reader.seekg(1, std::ios_base::cur);
		}
	}
}

void XMLReader::skipPrologs() 
{
	while (true)
	{
		skipWhitespace();

		std::vector<int> next(2);
		peek(next);

		if (next[0] != '<')
		{
			return;
		}

		if ((next[1] == '?') || (next[1] == '!'))
		{
			skipProlog();
		} else
		{
			break;
		}
	}
}

std::string XMLReader::readTag()
{
	skipWhitespace();

	char temp;
	std::string sb;

	while(peek() == 9)
		reader.read(&temp, 1);
	int next = peek();
	if (next != '<' || reader.eof())
	{
		return sb;
	}
	reader.read(&temp, 1);
	sb += temp;
	while (peek() != '>') {
		reader.read(&temp, 1);
		sb += temp;
	}
	reader.read(&temp, 1);
	sb += temp;

	reader.clear();
	return sb;
}

std::string XMLReader::readText()
{
	std::string sb;
	char temp;
	std::vector<int> next(cdata_start.size());

	peek(next);

	if (compareIntArrays(next, cdata_start) == true) 
	{
		// CDATA
		reader.seekg(next.size(), std::ios_base::cur);

		std::vector<int> buffer(cdata_end.size());
		while (true) 
		{
			peek(buffer);

			if (compareIntArrays(buffer, cdata_end) == true)
			{
				reader.seekg(buffer.size(), std::ios_base::cur);
				break;
			} else 
			{
				reader >> temp;
				sb += temp;
			}
		}
	} else 
	{
		// while next character is not a tag openner
		while (peek() != '<') 
		{
			// read in a character
			if(!reader.eof())
			{
				reader.read(&temp, 1);
				sb += temp;
			} else
			{
				break;
			}
		}
	}

	return sb;
}

bool XMLReader::compareIntArrays(const std::vector<int>& a1, const std::vector<int>& a2)
{
	if (a1.size() != a2.size()) 
	{
		return false;
	}

	for (unsigned int i=0; i < a1.size(); i++) 
	{
		if (a1[i] != a2[i]) 
		{
			return false;
		}
	}

	return true;
}